package goods

// 库存扣减 分布式事务扣减

import (
	"fmt"
	"io"
	"log"
	"os"
	"shop-component/common"
	"strconv"
	"time"

	"github.com/gomodule/redigo/redis"
)

// 定义Redis连接相关信息
const (
	redisAddr = "127.0.0.1:6379"
)

// DeductInventory 库存扣减函数，传入商品ID和要扣减的数量
func DeductInventory(productID string, quantity int) (bool, error) {
	// 连接Redis
	conn, err := redis.Dial("tcp", redisAddr)
	if err != nil {
		return false, err
	}
	defer conn.Close()

	return deductInventoryWithConn(conn, productID, quantity)
}

// DeductInventoryWithConn 库存扣减函数，传入商品ID和要扣减的数量以及Redis连接
func DeductInventoryWithConn(conn redis.Conn, productID string, quantity int) (bool, error) {
	return deductInventoryWithConn(conn, productID, quantity)
}

func deductInventoryWithConn(conn redis.Conn, productID string, quantity int) (bool, error) {
	// 定义锁的键和值，值可以用唯一标识客户端等信息
	lockKey := "inventory_lock_" + productID

	// 生成锁的值，这里使用服务器的IP地址作为锁的值
	lockValue := common.GetLocalIP()
	if lockValue == "" {
		lockValue = strconv.Itoa(int(time.Now().Unix()))
	}

	// 获取锁，设置过期时间（这里过期时间设为5秒，可根据实际情况调整）
	acquired, err := acquireLock(conn, lockKey, lockValue, 5)
	if err != nil {
		return false, err
	}
	if !acquired {
		return false, fmt.Errorf("未能获取到库存扣减锁，可能其他客户端正在操作")
	}

	// 获取当前库存数量
	currentInventory, err := getCurrentInventory(conn, productID)
	if err != nil {
		// 操作失败释放锁
		releaseLock(conn, lockKey, lockValue)
		return false, err
	}

	// 判断库存是否足够扣减
	if currentInventory < quantity {
		releaseLock(conn, lockKey, lockValue)
		return false, fmt.Errorf("库存不足，无法扣减")
	}

	// 执行库存扣减操作
	newInventory := currentInventory - quantity
	if err := setInventory(conn, productID, newInventory); err != nil {
		releaseLock(conn, lockKey, lockValue)
		return false, err
	}

	// 库存扣减成功，释放锁
	if _, err := releaseLock(conn, lockKey, lockValue); err != nil {
		return false, err
	}

	log.Println("库存扣减成功")
	return true, nil
}

// 获取当前库存数量
func getCurrentInventory(conn redis.Conn, productID string) (int, error) {
	reply, err := conn.Do("GET", "inventory_"+productID)
	if err != nil {
		return 0, err
	}
	inventory, err := redis.Int(reply, nil)
	if err != nil {
		return 0, err
	}
	return inventory, nil
}

// 设置库存数量
func setInventory(conn redis.Conn, productID string, quantity int) error {
	_, err := conn.Do("SET", "inventory_"+productID, quantity)
	return err
}

// 获取锁函数
func acquireLock(conn redis.Conn, lockKey, lockValue string, expiration int) (bool, error) {
	// 使用SET命令结合NX和EX（以秒为单位设置过期时间）实现原子操作获取锁并设置过期时间
	reply, err := conn.Do("SET", lockKey, lockValue, "NX", "EX", expiration)
	if err != nil {
		return false, err
	}
	ok, _ := redis.Bool(reply, nil)
	return ok, nil
}

func acquireLuaLock(conn redis.Conn, lockKey, lockValue string, expiration int) (bool, error) {
	// 读取Lua脚本内容
	luaScript, err := os.Open("./stock_decrease_lock.lua")
	if err != nil {
		return false, err
	}
	defer luaScript.Close()

	file, err := io.ReadAll(luaScript)
	if err != nil {
		return false, err
	}

	// 加载并执行Lua脚本
	script := redis.NewScript(len(string(file)), string(file))
	reply, err := script.Do(conn, lockKey, lockValue, expiration)
	if err != nil {
		return false, err
	}
	ok, _ := redis.Bool(reply, nil)
	return ok, nil
}

// 释放锁函数
func releaseLock(conn redis.Conn, lockKey, lockValue string) (bool, error) {
	// 先获取锁的值，验证是否是自己持有的锁
	reply, err := conn.Do("GET", lockKey)
	if err != nil {
		return false, err
	}
	value, _ := redis.String(reply, nil)
	if value == lockValue {
		// 如果是自己持有的锁，删除锁
		_, err := conn.Do("DEL", lockKey)
		if err != nil {
			return false, err
		}
		return true, nil
	}
	return false, nil
}
